<template>
	<view class="page-index">
		<view class="tips" :class="{ 'show':ajax.loading }" @click="getHistoryMsg">
			{{ajax.loadText}}
		</view>
		<view class="box-1" id="list-box">
			<view class="talk-list">
				<view v-for="(item,index) in talkList" :key="index" :id="`msg-${item.id}`">
					<view class="item" :class=" item.message_source">
						<image :src="item.user_avatar" mode="aspectFill" class="avatar"></image>
						<view class="content">
							<text v-if="item.message_content.type === 'text'">
								{{item.message_content.text}}
							</text>
						</view>
					</view>
					<view class="push-time">
						<text>{{item.push_time}}</text>
					</view>
				</view>
			</view>
		</view>
		<view class="box-2">
			<view class="item">
				<view class="item--content">
					<input type="text" class="content" v-model="content" placeholder="请输入聊天内容"
						placeholder-style="color:#DDD;" :cursor-spacing="6">
				</view>
				<button class="send" @click="send">发送</button>
			</view>
		</view>

		<up-gap height="80"></up-gap>
	</view>
</template>

<script setup lang="ts">
	import { onLoad, onPageScroll } from '@dcloudio/uni-app';
	import { nextTick, reactive, toRefs } from 'vue';

	const useMessageList = () => {
		const messageState = reactive({
			talkList: [] as any[],
			ajax: {
				rows: 20,	//每页数量
				page: 1,	//页码
				flag: true,	// 请求开关
				loading: true,	// 加载中
				loadText: '正在获取消息'
			},
			content: "", // 发消息内容
		});

		// 获取历史消息
		const getHistoryMsg = () => {
			if (!messageState.ajax.flag) {
				return; //
			}

			// 此处用到 ES7 的 async/await 知识，为使代码更加优美。不懂的请自行学习。
			let get = async () => {
				hideLoadTips();
				messageState.ajax.flag = false;
				let data = await joinHistoryMsg() as any;

				console.log('----- 模拟数据格式，供参考 -----');
				console.log(data);	// 查看请求返回的数据结构 

				// 获取待滚动元素选择器，解决插入数据后，滚动条定位时使用
				let selector = '';

				if (messageState.ajax.page > 1) {
					// 非第一页，则取历史消息数据的第一条信息元素
					selector = `#msg-${messageState.talkList[0].id}`;
				} else {
					// 第一页，则取当前消息数据的最后一条信息元素
					selector = `#msg-${data[data.length - 1].id}`;
				}

				// 将获取到的消息数据合并到消息数组中
				messageState.talkList = [...data, ...messageState.talkList];

				// 数据挂载后执行，不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
				console.log('selector:', selector);
				nextTick(() => {
					// 设置当前滚动的位置
					setPageScrollTo(selector);

					hideLoadTips(true);

					if (data.length < messageState.ajax.rows) {
						// 当前消息数据条数小于请求要求条数时，则无更多消息，不再允许请求。
						// 可在此处编写无更多消息数据时的逻辑
					} else {
						messageState.ajax.page++;

						// 延迟 200ms ，以保证设置窗口滚动已完成
						setTimeout(() => {
							messageState.ajax.flag = true;
						}, 200)
					}

				})
			}
			get();
		};
		// 拼接历史记录消息，正式项目可替换为请求历史记录接口
		const joinHistoryMsg = () => {
			let join = () => {
				let arr = [];

				//通过当前页码及页数，模拟数据内容
				let startIndex = (messageState.ajax.page - 1) * messageState.ajax.rows;
				let endIndex = startIndex + messageState.ajax.rows;
				for (let i = startIndex; i < endIndex; i++) {
					arr.push({
						"id": i,	// 消息的ID
						"message_content": {
							type: "text",
							text: `这是历史记录的第${i + 1}条消息`,
						},	// 消息内容
						"message_source": Math.random() > 0.5 ? 'push' : 'pull',// 此为消息类别，设 push 为发出去的消息，pull 为收到对方的消息,
						"push_time": "2024-11-15 11:29",
						"user_avatar": "/static/logo.png"	// 头像
					})
				}

				/*
					颠倒数组中元素的顺序。将最新的数据排在本次接口返回数据的最后面。
					后端接口按 消息的时间降序查找出当前页的数据后，再将本页数据按消息时间降序排序返回。
					这是数据的重点，因为页面滚动条和上拉加载历史的问题。
				 */
				arr.reverse();

				return arr;
			}

			// 此处用到 ES6 的 Promise 知识，不懂的请自行学习。
			return new Promise((done, fail) => {
				// 无数据请求接口，由 setTimeout 模拟，正式项目替换为 ajax 即可。
				setTimeout(() => {
					let data = join();
					done(data);
				}, 1500);
			})
		};
		// 设置页面滚动位置
		const setPageScrollTo = (selector : string) => {
			let view = uni.createSelectorQuery().select(selector);
			view.boundingClientRect((res : any) => {
				uni.pageScrollTo({
					scrollTop: res.top - 30,	// -30 为多显示出大半个消息的高度，示意上面还有信息。
					duration: 0
				});
			}).exec();
		};
		// 隐藏加载提示
		const hideLoadTips = (flag = false) => {
			if (flag) {
				messageState.ajax.loadText = '消息获取成功';
				setTimeout(() => {
					messageState.ajax.loading = false;
				}, 300);
			} else {
				messageState.ajax.loading = true;
				messageState.ajax.loadText = '正在获取消息';
			}
		};
		// 发送信息
		const send = () => {
			if (!messageState.content) {
				uni.showToast({
					title: '请输入有效的内容',
					icon: 'none'
				})
				return;
			}

			uni.showLoading({
				title: '正在发送'
			})
			setTimeout(() => {
				uni.hideLoading();

				// 将当前发送信息 添加到消息列表。
				let data = {
					"id": new Date().getTime(),
					"message_content": {
						type: "text",
						text: messageState.content,
					},
					"message_source": "push",
					"push_time": "2024-11-15 11:29",
					"user_avatar": "/static/logo.png"
				}
				messageState.talkList.push(data);

				nextTick(() => {
					// 清空内容框中的内容
					messageState.content = '';
					uni.pageScrollTo({
						scrollTop: 999999,	// 设置一个超大值，以保证滚动条滚动到底部
						duration: 0
					});
				})
			}, 1500);
		}

		return {
			messageState,
			getHistoryMsg,
			joinHistoryMsg,
			setPageScrollTo,
			hideLoadTips,
			send,
		};
	}
	const { messageState, getHistoryMsg, send } = useMessageList();
	const { talkList, ajax, content } = toRefs(messageState);

	onLoad((query) => {
		nextTick(() => {
			getHistoryMsg();
		});

		const scene = decodeURIComponent(query.scene)
		console.log('scene:', scene)
	});
	onPageScroll((e) => {
		if (e.scrollTop < 5) {
			getHistoryMsg();
		}
	});
</script>

<style lang="scss" scoped>
	.page-index {
		background-color: #F3F3F3;
		font-size: 14px;
	}

	/* 加载数据提示 */
	.tips {
		position: fixed;
		left: 0;
		top: var(--window-top);
		width: 100%;
		z-index: 9;
		background-color: rgba(0, 0, 0, 0.15);
		height: 36px;
		line-height: 36px;
		transform: translateY(-40px);
		transition: transform 0.3s ease-in-out 0s;
		color: #ffffff;
		font-size: 12px;
		text-align: center;

		&.show {
			transform: translateY(0);
		}
	}

	.box-1 {
		width: 100%;
		height: auto;
		padding-bottom: 50px;
		box-sizing: content-box;

		/* 兼容iPhoneX */
		margin-bottom: 0;
		margin-bottom: constant(safe-area-inset-bottom);
		margin-bottom: env(safe-area-inset-bottom);
	}

	.box-2 {
		position: fixed;
		left: 0;
		width: 100%;
		bottom: 0;
		height: auto;
		z-index: 2;
		border-top: #e5e5e5 solid 1px;
		box-sizing: content-box;
		background-color: #F3F3F3;

		/* 兼容iPhoneX */
		padding-bottom: 0;
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);

		.item {
			display: flex;
			flex-direction: row;
			flex-wrap: nowrap;
			justify-content: flex-start;
			align-items: center;
			align-content: center;

			&--content {
				width: 0;
				flex-grow: 1;
			}
		}

		>view {
			padding: 0 10px;
			height: 50px;
		}

		.content {
			background-color: #fff;
			height: 32px;
			padding: 0 10px;
			border-radius: 16px;
			font-size: 14px;
		}

		.send {
			background-color: #42b983;
			color: #fff;
			height: 32px;
			margin-left: 10px;
			border-radius: 16px;
			padding: 0;
			width: 60px;
			line-height: 30px;

			&:active {
				background-color: #5fc496;
			}
		}
	}

	.talk-list {
		padding-bottom: 10px;

		/* 消息项，基础类 */
		.item {
			display: flex;
			flex-direction: row;
			flex-wrap: nowrap;
			justify-content: flex-start;
			align-items: center;
			align-content: center;
			padding: 10px 10px 0 10px;
			align-items: flex-start;
			align-content: flex-start;
			color: #333;

			.avatar {
				width: 46px;
				height: 46px;
				border-radius: 50%;
				border: #fff solid 1px;
			}

			.content {
				padding: 10px;
				border-radius: 4px;
				max-width: 250px;
				word-break: break-all;
				line-height: 26px;
				position: relative;
			}

			/* 收到的消息 */
			&.pull {
				.content {
					margin-left: 16px;
					background-color: #fff;

					&::after {
						content: '';
						display: block;
						width: 0;
						height: 0;
						border-top: 8px solid transparent;
						border-bottom: 8px solid transparent;
						border-right: 10px solid #fff;
						position: absolute;
						top: 15px;
						left: -9px;
					}
				}
			}

			/* 发出的消息 */
			&.push {
				/* 主轴为水平方向，起点在右端。使不修改DOM结构，也能改变元素排列顺序 */
				flex-direction: row-reverse;

				.content {
					margin-right: 16px;
					background-color: #a0e959;

					&::after {
						content: '';
						display: block;
						width: 0;
						height: 0;
						border-top: 8px solid transparent;
						border-bottom: 8px solid transparent;
						border-left: 10px solid #a0e959;
						position: absolute;
						top: 15px;
						right: -9px;
					}
				}
			}
		}

		.push-time {
			text-align: center;
			font-size: 12px;
			color: #a29696;
		}
	}
</style>